# -*- coding: utf-8 -*-
"""
中性策略框架 | 邢不行 | 2024分享会
author: 邢不行
微信: xbx6660
"""
import ccxt
import time
import traceback
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_rows', 1000)
pd.set_option('expand_frame_repr', False)  # 当列太多时不换行
pd.set_option('display.unicode.ambiguous_as_wide', True)  # 设置命令行输出时的列对齐功能
pd.set_option('display.unicode.east_asian_width', True)
from api.market import load_market, fetch_binance_ticker_data
from api.trade import place_order, get_twap_symbol_info_list
from api.position import get_spot_position, bn_transfer_spot_to_u
from utils.dingding import send_wechat_work_msg
from utils.functions import get_account_info
from config import account_config, error_webhook_url, exchange_basic_config


# ===监控间隔(单位: 秒)
monitor_time = 60  # 60：表示每60秒监测一次。注意：检测过于频繁会消耗请求权重
# ===监控合约账户保证金率阈值
stop_equity_pnl = 0.5
# ===监控平仓比例。0.1: 每次达到保证金率阈值，现货和合约的平仓10%，并将剩余的U转入U本位合约账户
stop_rate = 0.1
# 获取数据的公共交易所对象
common_exchange = ccxt.binance(exchange_basic_config)


def run():
    while True:
        # =====获取合约与现货的信息。最小下单量，下单精度等
        # =获取U本位合约交易对的信息
        swap_symbol_list, swap_min_qty, swap_price_precision, swap_min_notional = load_market(common_exchange, symbol_type='swap')
        # =加载现货交易对信息
        spot_symbol_list, spot_min_qty, spot_price_precision, spot_min_notional = load_market(common_exchange, symbol_type='spot')

        # =====获取账户信息,并且筛选含有现货的账户
        update_account_info = get_account_info(account_config.copy(), is_only_spot_account=True, is_operate=False)
        # 判断是否没能成功读取任一账户
        if not len(update_account_info.keys()):  # 如果update_account_info数据为空，表示更新账户信息失败
            print(f'monitor脚本 所有账户更新信息失败，{monitor_time}后重试······')
            time.sleep(monitor_time)
            continue

        # =====遍历所有账号监测
        for account_name, account_info in update_account_info.items():
            # ===获取账号的配置
            exchange = account_info['exchange']
            spot_position = account_info['spot_position']
            swap_position = account_info['swap_position']
            swap_equity = account_info['swap_equity']
            max_one_order_amount = account_info['max_one_order_amount']
            twap_interval = account_info['twap_interval']
            wechat_webhook_url = account_info['wechat_webhook_url']

            # ===判断当前是否有仓位
            if spot_position.empty or swap_position.empty:  # 当前没有仓位直接跳过
                print('当前没有【现货持仓】 或 【合约持仓】')
                continue

            # ===计算账户保证金率
            total_equity = swap_equity + swap_position['持仓盈亏'].sum()  # 计算当前账户总净值（含未实现盈亏）
            pos_equity = (swap_position['当前标记价格'] * swap_position['当前持仓量']).abs().sum()  # 计算合约账户持仓价值
            margin_rate = total_equity / pos_equity  # 计算当前账户保证金率
            print(f'当前账户资金: {total_equity}，合约账户持仓价值abs: {pos_equity}，账户保证金率: {margin_rate}')

            # 判断当前账户的保证金率是否达到阈值
            if margin_rate > stop_equity_pnl:
                print(f'账户保证金率 {margin_rate} 大于 监控阈值 {stop_equity_pnl} , sleep {monitor_time}s 后，继续监控······')
                continue

            # ===计算下单信息
            swap_position['实际下单量'] = swap_position['当前持仓量'] * stop_rate * -1  # 直接通过当前持仓进行计算，方向要去反
            swap_position['实际下单资金'] = swap_position['实际下单量'] * swap_position['当前标记价格']  # 计算实际下单资金，用于后续拆单
            swap_position['交易模式'] = '减仓'  # 设置交易模式
            swap_position = swap_position[abs(swap_position['实际下单量']) > 0]  # 保留实际下单量 > 0 的数据
            swap_position.reset_index(inplace=True)
            print('合约下单信息：\n', swap_position)

            spot_position['实际下单量'] = spot_position['当前持仓量'] * stop_rate * -1  # 直接通过当前持仓进行计算，方向要去反
            spot_position['实际下单资金'] = spot_position['实际下单量'] * spot_position['当前价格']  # 计算实际下单资金，用于后续拆单
            spot_position['交易模式'] = '减仓'  # 设置交易模式
            spot_position = spot_position[abs(spot_position['实际下单量']) > 0]  # 保留实际下单量 > 0 的数据
            spot_position.reset_index(inplace=True)
            print('现货下单信息：\n', spot_position)

            # 判断是否需要有下单信息
            if swap_position.empty or spot_position.empty:
                continue

            # 记录一下是否会下单
            is_order = spot_position['实际下单资金'].abs().max() > 10 or swap_position['实际下单资金'].abs().max() > 5

            # ===使用twap算法拆分订单
            twap_swap_symbol_info_list = get_twap_symbol_info_list(swap_position, max_one_order_amount)
            twap_spot_symbol_info_list = get_twap_symbol_info_list(spot_position, max_one_order_amount)

            # ===遍历下单
            for i in range(len(twap_swap_symbol_info_list)):
                # 获取币种的最新价格
                symbol_last_price = fetch_binance_ticker_data(exchange)
                # 逐批下单
                place_order(exchange, twap_swap_symbol_info_list[i], symbol_last_price, swap_min_qty,
                            swap_price_precision, swap_min_notional, symbol_type='swap', wechat_webhook_url=wechat_webhook_url)
                # 下单间隔
                print(f'等待 {twap_interval}s 后继续下单')
                time.sleep(twap_interval)

            # ===现货遍历下单
            for i in range(len(twap_spot_symbol_info_list)):
                # 获取现货币种的最新价格
                symbol_last_price = fetch_binance_ticker_data(exchange, symbol_type='spot')
                # 逐批下单
                place_order(exchange, twap_spot_symbol_info_list[i], symbol_last_price, spot_min_qty,
                            spot_price_precision, spot_min_notional, symbol_type='spot', wechat_webhook_url=wechat_webhook_url)
                # 下单间隔
                print(f'等待 {twap_interval}s 后继续下单')
                time.sleep(twap_interval)

            # ===将现货中的U转入的合约账号
            spot_position = get_spot_position(exchange)
            if 'USDT' in spot_position['symbol'].to_list():
                usdt = spot_position.loc[spot_position['symbol'] == 'USDT', '当前持仓量'].iloc[0]
                # 转资金到合约账户
                bn_transfer_spot_to_u(exchange, round(usdt - 1, 1))

            # ===发送信息推送
            if is_order:
                send_wechat_work_msg(f'成功减仓完毕，已将现货的USDT转入U本位合约账户补充保证金', wechat_webhook_url)

            # ===休息一会儿
            time.sleep(2)

        # 本次循环结束
        print('-' * 20, '本次监测结束，%f秒后进入下一次监测' % monitor_time, '-' * 20)
        print('\n')
        time.sleep(monitor_time)


if __name__ == '__main__':
    # =====检查监控配置
    # ===检查平仓比例配置
    if stop_rate > 1:
        print('平仓比例配置错误，不支持 1 以上的参数配置')
        exit()
    # ===检查合约账户浮动盈亏阈值配置
    if stop_equity_pnl > 1:
        print('合约账户保证金率阈值配置错误，不支持 1 以上的参数配置')
        exit()

    # ===主程序入口
    while True:
        try:
            run()
        except KeyboardInterrupt:  # 手动终止程序的错误，直接打印'退出'，并退出程序
            print('退出')
            exit()
        except Exception as err:
            msg = '保3监控脚本出错，10s之后重新运行，出错原因: ' + str(err)
            print(msg)
            print(traceback.format_exc())
            send_wechat_work_msg(msg, error_webhook_url)
            time.sleep(10)
